import { MeshBasicMaterial, Mesh, Vector3, Plane, DoubleSide, PlaneGeometry, MathUtils, Matrix4, Matrix3 } from "three";
import { TransformControls } from "three/examples/jsm/controls/TransformControls";
import { App } from "../../application";
import { Point } from "../../Engine";
import Helper from "../../utils/Helper";

/**
 * 刨切分析
 */
export default class ClipAnalysis {
    private app: App;
    private data: { planeMsg: { planePoint: Point, rotation: { x: number, y: number, z: number, angle: number }, planeWidth: number, opacity: number }, clipObjs: [] };
    private planeNormal = new Vector3(0, 0, -1); //裁剪平面的法线, 注意这里是初始化时候的法线
    private planeMesh: Mesh;
    private clipPlane;
    private transformControls: TransformControls;

    /**
     * 刨切分析
     * @example let planePoint = new GE.Point(13377184.375437068, 3540339.2581293588, 100, '3857');
        let model = //加载一个模型，比如：FBX, gltf, 或者就是一个mesh对象
        let data = {planeMsg: {planePoint: planePoint, rotation: {x: 1,y: 0,z:0, angle:90}, planeWidth: 1000, opacity: 0.3}, clipObjs:[model]};
        let clipAnalysis = new GE.ClipAnalysis();
        clipAnalysis.openAnalysis(data); //开启刨切分析
        //clipAnalysis.closeAnalysis(); //关闭刨切分析
     */
    public constructor() {
        this.app = App.getInstance();
    }

    /**
     * 开启刨切分析
     * @param data 刨切分析需要用到的参数
     * @param data.planeMsg 刨切分析需要用到的平面参数, 这个平面是用点法线表示, 默认法线是: let normal = new Vector3(0, 0, -1)
     * @param data.planeMsg.planePoint 刨切平面上的一个点
     * @param data.planeMsg.rotation 对刨切平面默认的法线做旋转。x,y,z分别表示旋转轴，angle表示旋转角度
     * @param data.planeMsg.planeWidth 刨切平面的宽度，默认宽度是1000
     * @param data.planeMsg.opacity 刨切平面的透明度
     * @param data.clipObjs 被刨切的模型数组
     * @example let planePoint = new GE.Point(13377184.375437068, 3540339.2581293588, 100, '3857');
        let model = //加载一个模型，比如：FBX, gltf, 或者就是一个mesh对象
        let data = {planeMsg: {planePoint: planePoint, rotation: {x: 1,y: 0,z:0, angle:90}, planeWidth: 1000, opacity: 0.3}, clipObjs:[model]};
        clipAnalysis.openAnalysis(data);
     */
    public openAnalysis(data: { planeMsg: { planePoint: Point, rotation: { x: number, y: number, z: number, angle: number }, planeWidth: number, opacity: number }, clipObjs: [] }) {
        this.data = data;
        this.data.planeMsg.planePoint.toEPSGWeb();

        let rotationMsg = this.data.planeMsg.rotation;
        var axis = new Vector3(rotationMsg.x, rotationMsg.y, rotationMsg.z);
        this.planeNormal.applyAxisAngle(axis, MathUtils.degToRad(rotationMsg.angle));

        this.clipPlane = this.getPlaneArray();
        this.clip(this.clipPlane);
        this.buildMesh();
    }

    /**
     * 关闭刨切分析
     * @example clipAnalysis.closeAnalysis();
     */
    public closeAnalysis() {
        let meshList = this.data.clipObjs;
        let self = this;
        meshList.forEach(element => {
            self.setMeshClip(element, null);
        });

        this.data = null;
        this.app.remove(this.planeMesh);
        this.planeMesh = null;

        this.hideControl();
    }

    /**
     * 设置刨切控制器的模式
     * @param model 控制器的模式 'translate': 表示平移模式，'rotate': 表示旋转模式
     * @example clipAnalysis.setControlModel('rotate'); //设置刨切控制器为旋转模式
     * clipAnalysis.setControlModel('translate'); //设置刨切控制器为平移模式
     */
    public setControlModel(model: 'translate' | 'rotate') {
        if (!this.transformControls) {
            this.setTransformControl();
        }
        this.transformControls.attach(this.planeMesh);
        this.transformControls.setMode(model);
    }

    /**
     * 隐藏控制器
     * @example clipAnalysis.hideControl(); 
     */
    public hideControl() {
        if (!this.transformControls) {
            return;
        }
        
        this.app.remove(this.transformControls.getHelper());
        this.transformControls.detach();
        this.transformControls.dispose();
        this.transformControls = null;
    }

    private buildMesh() {
        let clipPlaneWidth = this.data.planeMsg.planeWidth || 1000;
        let planeGeo = new PlaneGeometry(clipPlaneWidth, clipPlaneWidth, 100, 100);
        let opacity = this.data.planeMsg.opacity || 0.3;
        let planeMaterial = new MeshBasicMaterial({
            transparent: true, opacity: opacity, depthTest: true, depthWrite: true, color: '#FFFFFF',
            side: DoubleSide
        });
        this.planeMesh = new Mesh(planeGeo, planeMaterial);
        this.planeMesh.position.copy(this.data.planeMsg.planePoint);

        let rotationMsg = this.data.planeMsg.rotation;
        var axis = new Vector3(rotationMsg.x, rotationMsg.y, rotationMsg.z);
        this.planeMesh.rotateOnAxis(axis, MathUtils.degToRad(rotationMsg.angle));

        this.app.add(this.planeMesh);
    }

    private clip(clipPlaneArray) {
        let self = this;
        let meshList = this.data.clipObjs;
        meshList.forEach(element => {
            self.setMeshClip(element, clipPlaneArray);
        });
    }

    private setMeshClip(mesh, clipPlaneArray) {
        let self = this;
        Helper.traverseObject(mesh, function (obj) {
            if (obj instanceof TransformControls || self.planeMesh == obj) {
                return false;
            }

            if (obj instanceof Mesh || obj.type == 'Mesh') {
                if (obj.material.length > 0) {
                    obj.material.forEach(element => {
                        element.clippingPlanes = clipPlaneArray;
                    });
                } else {
                    obj.material.clippingPlanes = clipPlaneArray;
                }

            }
            return true;
        });
    }

    protected getPlaneArray() {
        let distance = this.data.planeMsg.planePoint.clone().dot(this.planeNormal);
        let plane = new Plane(this.planeNormal.clone().multiplyScalar(1), -distance);

        return [plane];
    }

    private setTransformControl() {
        this.transformControls = new TransformControls(this.app.three.camera, this.app.three.renderer.domElement);
        this.transformControls.enabled = true;
        // this.transformControls.attach(this.planeMesh);
        Helper.traverseObject(this.transformControls, function (element) {
            if (element instanceof Mesh || element.type == 'Mesh') {
                element.material.clippingPlanes = null;
            }
        });
        let self = this;
        this.transformControls.addEventListener('rotationAngle-changed', function (event) {
            let modelMatrix: Matrix4 = event.target.object.matrixWorld;
            let originNormal = new Vector3(0, 0, -1).normalize();
            let array = modelMatrix.toArray();
            let rotationArray = [array[0], array[1], array[2], array[4], array[5], array[6], array[8], array[9], array[10]];
            let rotationMatrix = new Matrix3().fromArray(rotationArray);
            self.planeNormal = originNormal.applyMatrix3(rotationMatrix).normalize(); //self.planeNormal
            self.clipPlane = self.getPlaneArray();
            self.clip(self.clipPlane);
        });
        this.transformControls.addEventListener('dragging-changed', function (event) {
            self.app.camera.orbit.enabled = !event.value;
        });
        this.transformControls.addEventListener('change', function (event) {
            if(!event.target.object){
                return;
            }
            if(event.target.object.position.equals(self.data.planeMsg.planePoint)){
                return;
            }
            let targetPosition = event.target.object.position;
            self.data.planeMsg.planePoint = new Point(targetPosition.x, targetPosition.y, targetPosition.z, '3857')
            self.clipPlane = self.getPlaneArray();
            self.clip(self.clipPlane);
        });
        this.app.add(this.transformControls.getHelper());
    }
}